#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>

/* OpenGL part 1*/
#include <X11/X.h>
#include <X11/keysymdef.h>
#include <X11/Xlib.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glu.h>

Display* dpy;
Window root;
GLint att[] = {GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None};
XVisualInfo* vi;
Colormap cmap;
XSetWindowAttributes swa;
Window win;
GLXContext glc;
XWindowAttributes gwa;
XEvent xev;

void DrawPrepare() {
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};

void DrawDot(float x, float y, float size, float r, float g, float b) {
	glBegin(GL_QUADS);
	glColor3f(r, g, b); glVertex3f(x, y, 0.0);
	glColor3f(r, g, b); glVertex3f(x+size, y, 0.0);
	glColor3f(r, g, b); glVertex3f(x+size, y+size, 0.0);
	glColor3f(r, g, b); glVertex3f(x, y+size, 0.0);
	glEnd();
};
/* OpenGL part 1 end */

double sigm(double x) {
  return 1.0f/(1+exp(-x));
};

/* dsigm(x) = sigm(x) * (1-sigm(x)) */

int main() {
	/* seed */
  srand(time(NULL));

  /* OpenGL part 2 */
  dpy = XOpenDisplay(NULL);
	if(dpy == NULL) {
		printf("\n\tCannot connect to X server\n\n");
		exit(0);
	};

	root = DefaultRootWindow(dpy);
	vi = glXChooseVisual(dpy, 0, att);
	if(vi == NULL) {
		printf("\n\tno appropriate visual found\n\n");
		exit(0);
	} else {
		printf("\n\tvisual %p selected\n", (void*)vi->visualid);
	};

	cmap = XCreateColormap(dpy, root, vi->visual, AllocNone);
	swa.colormap = cmap;
	swa.event_mask = KeyPressMask;
	win = XCreateWindow(dpy, root, 0, 0, 600, 600, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &swa);
	XMapWindow(dpy, win);
	XStoreName(dpy, win, "OpenGL and X");
	glc = glXCreateContext(dpy, vi, NULL, GL_TRUE);
	glXMakeCurrent(dpy, win, glc);

	glEnable(GL_DEPTH_TEST);
  /* OpenGL part 2 end */

	/* nodes */
	double x0[3];
	double x1[3];
	double x2[2];

	/* weights */
	double w0[3][2];
	double w1[3][2];

	/* deltas for weights */
	double dw0[3][2];
	double dw1[3][2];

	/* deltas */
	double delta2[2];
	double delta1[2];

	/* alpha and beta (memory and learning speed) */
	double alpha = 0.9f;
	double beta = 0.7f;

	/* input data */
	double t[2];

	/* output values (that should be)*/
	double rx[2];

	/* learning set: (0,0)->(0,0), (0,1)->(1,0), (1,0)->(1,0), (1,1)->(0,0) */
	double lset[] = {0.0f, 0.0f, 0.0f, 0.0f,
									 0.0f, 1.0f, 1.0f, 0.0f,
								   1.0f, 0.0f, 1.0f, 0.0f,
							     1.0f, 1.0f, 0.0f, 0.0f };

	/* init weights and delta weights */
	for(int i=0; i<3; i++)
		for(int j=0; j<2; j++) {
			w0[i][j] = (double)(rand() % 1000 - 500) / 10000.0f;
			w1[i][j] = (double)(rand() % 1000 - 500) / 10000.0f;
			dw0[i][j] = 0.0f;
			dw1[i][j] = 0.0f;
		};

	/* init bias nodes */
	x0[2] = 1.0f;
	x1[2] = 1.0f;

  /* OpenGL part 3 */
  while(1) {
		if(XCheckTypedWindowEvent(dpy, win, KeyPress, &xev)) {
			KeySym sym = XLookupKeysym(&xev.xkey, 0);
      switch(sym) {
        default:
					glXMakeCurrent(dpy, None, NULL);
					glXDestroyContext(dpy, glc);
					XDestroyWindow(dpy, win);
					XCloseDisplay(dpy);
					exit(0);
					break;
      };
    };

		XGetWindowAttributes(dpy, win, &gwa);

    DrawPrepare();


	/* learning */
	/* for(int m=0; m<100000; m++) /* 1000 epochs */
		for(int k=0; k<16; k+=4) { /* four pairs (t, rx) */
			t[0] = lset[k];
			t[1] = lset[k+1];
			rx[0] = lset[k+2];
			rx[1] = lset[k+3];

			/* forward */
			x0[0] = t[0]; //sigm(t[0]);
			x0[1] = t[1]; //sigm(t[1]);

			x1[0] = sigm(x0[0]*w0[0][0] + x0[1]*w0[1][0] + x0[2]*w0[2][0]);
			x1[1] = sigm(x0[0]*w0[0][1] + x0[1]*w0[1][1] + x0[2]*w0[2][1]);

			x2[0] = sigm(x1[0]*w1[0][0] + x1[1]*w1[1][0] + x1[2]*w1[2][0]);
			x2[1] = sigm(x1[0]*w1[0][1] + x1[1]*w1[1][1] + x1[2]*w1[2][1]);

      /* show result */
      /* printf("(%f, %f)->(%f, %f)\n", t[0], t[1], x2[0], x2[1]); */

			/* calc deltas (backpropagation) */
			delta2[0] = (x2[0] - rx[0]) * x2[0] * (1 - x2[0]);
			delta2[1] = (x2[1] - rx[1]) * x2[1] * (1 - x2[1]);

			delta1[0] = (delta2[0]*w1[0][0] + delta2[1]*w1[0][1]) * x1[0] * (1 - x1[0]);
			delta1[1] = (delta2[0]*w1[1][0] + delta2[1]*w1[1][1]) * x1[1] * (1 - x1[1]);

			/* calc delta weights */
      for(int i=0; i<3; i++)
        for(int j=0; j<2; j++)
			     dw1[i][j] = alpha*dw1[i][j] - beta*x1[i]*delta2[j];

       for(int i=0; i<3; i++)
         for(int j=0; j<2; j++)
			      dw0[i][j] = alpha*dw0[i][j] - beta*x0[i]*delta1[j];

			/* change weights */
      for(int i=0; i<3; i++)
        for(int j=0; j<2; j++) {
			     w1[i][j] += dw1[i][j];
           w0[i][j] += dw0[i][j];
         };

		};

    /* forward and paint dots */
    for(double y=-1.0f; y<= 1.0f; y+=0.01f)
      for(double x=-1.0f; x<=1.0f; x+=0.01f) {
        x0[0] = (x+1.0f)*0.5f; //sigm(t[0]);
        x0[1] = (y+1.0f)*0.5f; //sigm(t[1]);

        x1[0] = sigm(x0[0]*w0[0][0] + x0[1]*w0[1][0] + x0[2]*w0[2][0]);
        x1[1] = sigm(x0[0]*w0[0][1] + x0[1]*w0[1][1] + x0[2]*w0[2][1]);

        x2[0] = sigm(x1[0]*w1[0][0] + x1[1]*w1[1][0] + x1[2]*w1[2][0]);
        x2[1] = sigm(x1[0]*w1[0][1] + x1[1]*w1[1][1] + x1[2]*w1[2][1]);
        DrawDot(x, y, 0.01f, x2[0], x2[1], 0.0f);
      };

    glViewport(0, 0, gwa.width, gwa.height);
    glXSwapBuffers(dpy, win);

  };
  return 0;
};
